﻿/*================================================================================================================================

  This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.  

  THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, 
  INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.  

  We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object 
  code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software 
  product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the 
  Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims 
  or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

 =================================================================================================================================*/

using LINQPad.Extensibility.DataContext;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.CSharp;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Pfe.Xrm.Common;
using Microsoft.Pfe.Xrm.View;
using Microsoft.Xrm.Sdk.Discovery;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;

namespace Microsoft.Pfe.Xrm.ViewModel
{
    public class MainWindowViewModel : ViewModelBase
    {
        #region Property

        #region Visibility

        private bool isLoginVisible;
        public bool IsLoginVisible
        {
            get { return isLoginVisible; }
            set
            {
                isLoginVisible = value;
                NotifyPropertyChanged();
            }
        }

        private bool isAutoRegister;
        public bool IsAutoRegister
        {
            get { return isAutoRegister; }
            set
            {
                isAutoRegister = value;                
                NotifyPropertyChanged();
            }
        }
        
        #endregion
      
        // Loading Message holder
        private string loadMessage;
        public string LoadMessage
        {
            get { return loadMessage; }
            set
            {
                loadMessage = value;
                NotifyPropertyChanged();
            }
        }

        // Message holder
        private string message;
        public string Message
        {
            get { return message; }
            set
            {
                message = value;
                NotifyPropertyChanged();
            }
        }     

        // ClientId
        public string ClientId
        {
            get { return props.ClientId; }
            set
            {
                props.ClientId = value;
                NotifyPropertyChanged();
            }
        }

        public bool IsLoaded;
        private CrmProperties props;
        private string contextName;
        private bool useCurrentUser;
        private string metadataFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "$metadata.csdl");

        #endregion

        #region Method

        /// <summary>
        /// Constructor
        /// </summary>
        public MainWindowViewModel(IConnectionInfo cxInfo, bool isNewConnection)
        {
            // Display message depending on isNewConnection
            Message = isNewConnection ? "Click Login to CRM." : "Click Reload Metadata to update Schema";            

            // Use Auto Register and Current User credential by default.
            IsAutoRegister = true;
            useCurrentUser = true;

            // Change button visibility depending on if this is New Connection or not.
            if(isNewConnection)
                IsLoginVisible = true;
          
            props = new CrmProperties(cxInfo);
        }        

        /// <summary>
        /// Launch CrmLogin and let user login, then set CrmProperties.
        /// </summary>
        private async void LoginToCrm()
        {
            // Establish the Login control
            CrmLogin ctrl = new CrmLogin();
            // Wire Event to login response. 
            ctrl.ConnectionToCrmCompleted += ctrl_ConnectionToCrmCompleted;
            // Show the dialog. 
            ctrl.ShowDialog();

            await Task.Delay(1);

            // Handel return. 
            if (ctrl.CrmConnectionMgr != null && ctrl.CrmConnectionMgr.CrmSvc != null && ctrl.CrmConnectionMgr.CrmSvc.IsReady)
            {
                IsLoading = true;
                LoadMessage = "Signing to Dynamics CRM....";

                // Assign local property
                props.OrgUri = ctrl.CrmConnectionMgr.ConnectedOrgPublishedEndpoints[EndpointType.WebApplication];
                                
                props.FriendlyName = ctrl.CrmConnectionMgr.CrmSvc.ConnectedOrgFriendlyName;
                props.AuthenticationProviderType = ctrl.CrmConnectionMgr.CrmSvc.OrganizationServiceProxy.ServiceConfiguration.AuthenticationType.ToString();
                props.Authority = DiscoveryAuthority(new Uri(props.OrgUri + "/XRMServices/2011/Organization.svc/web?SdkClientVersion=6.1.0000.0000"));

                // Store User Credentials.
                ClientCredentials credentials = ctrl.CrmConnectionMgr.CrmSvc.OrganizationServiceProxy.ClientCredentials;
                if (credentials.UserName.UserName != null)
                {
                    props.UserName = credentials.UserName.UserName;
                    props.Password = credentials.UserName.Password;
                }
                else if (credentials.Windows.ClientCredential.UserName != null)
                {
                    props.DomainName = credentials.Windows.ClientCredential.Domain;
                    props.UserName = credentials.Windows.ClientCredential.UserName;
                    props.Password = credentials.Windows.ClientCredential.Password;
                }
               
                // Update the message for next action.
                Message = "Click Register App to get ClientID, or manually enter it.";

                IsLoading = false;
            }
            else
                MessageBox.Show("BadConnect");
        }

        /// <summary>
        /// Raised when the login form process is completed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctrl_ConnectionToCrmCompleted(object sender, EventArgs e)
        {
            if (sender is CrmLogin)
            {
                ((CrmLogin)sender).Close();
            }
        }

        /// <summary>
        /// Discover the authority for authentication.
        /// </summary>
        /// <param name="serviceUrl">The SOAP endpoint for a tenant organization.</param>
        /// <returns>The decoded authority URL.</returns>
        /// <remarks>The passed service URL string must contain the SdkClientVersion property.
        /// Otherwise, the discovery feature will not be available.</remarks>
        public String DiscoveryAuthority(Uri serviceUrl)
        {
            // Use AuthenticationParameters to send a request to the organization's endpoint and
            // receive tenant information in the 401 challenge. 
            AuthenticationParameters parameters = null;

            HttpWebResponse response = null;
            try
            {
                // Create a web request where the authorization header contains the word "Bearer".
                HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
                httpWebRequest.Headers.Add(HttpRequestHeader.Authorization.ToString(), "Bearer");

                // The response is to be encoded.
                httpWebRequest.ContentType = "application/x-www-form-urlencoded";
                response = (HttpWebResponse)httpWebRequest.GetResponse();

                parameters = AuthenticationParameters.CreateFromUnauthorizedResponse(response);
            }
            catch (WebException ex)
            {
                response = (HttpWebResponse)ex.Response;
                if (response == null)
                {

                }
                else
                {
                    // A good response was returned. Extract any parameters from the response.
                    // The response should contain an authorization_uri parameter.
                    parameters = AuthenticationParameters.CreateFromUnauthorizedResponse(response);
                }
            }
            finally
            {
                if (response != null)
                    response.Close();
            }

            // Return the authority URL.
            return parameters.Authority;
        }

        /// <summary>
        /// Download REST Endpoint metadata by using OAuth 2.0 or Windows Authentication  set metadata file path.
        /// </summary>
        private async Task DownloadMetadata()
        {
            string metadata = "";

            // Use OAuth 2.0 for CRMOL
            if (props.AuthenticationProviderType == "OnlineFederation")
            {
                AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common");
                authContext.TokenCache.Clear();
                // User Crm User and Password
                var credential = new UserCredential(props.UserName, props.Password);

                AuthenticationResult result = null;

                // Try to get AuthenticationResult in a loop. If application is just registered to Azure AD,
                // it sometime fails to find the application and crashes.
                while (true)
                {
                    try
                    {
                        result = authContext.AcquireToken(props.OrgUri, props.ClientId, credential);
                    }
                    catch
                    {
                        // If something went wrong, try a sec later.
                        // Cannot use await in catch block (C# 5.0).
                        Thread.Sleep(1000);
                    }

                    // Exit loop once you get AccessToken.
                    if (result != null && !string.IsNullOrEmpty(result.AccessToken))
                        break;
                }

                using (HttpClient httpClient = new HttpClient())
                {
                    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                    var response = await httpClient.GetAsync(props.OrgUri + "/XRMServices/2011/OrganizationData.svc/$metadata");
                    metadata = await response.Content.ReadAsStringAsync();
                }
            }
            // User Windows Integrated Authentication for OnPremise
            else
            {
                HttpClientHandler handler = new HttpClientHandler();
                if (String.IsNullOrEmpty(props.DomainName))
                    handler.Credentials = CredentialCache.DefaultCredentials;
                else
                    handler.Credentials = new NetworkCredential(props.DomainName + "//" + props.UserName, props.Password);

                using (HttpClient httpClient = new HttpClient(handler))
                {
                    var response = await httpClient.GetAsync(props.OrgUri + "/XRMServices/2011/OrganizationData.svc/$metadata");
                    metadata = await response.Content.ReadAsStringAsync();
                }
            }

            // Delete file if exist
            File.Delete(metadataFilePath);

            // Create file.
            File.WriteAllText(metadataFilePath, metadata, Encoding.UTF8);
        }

        #region Register Azure AD Application

        private AuthenticationContext authContext;

        /// <summary>
        /// Register Application to Azure AD
        /// </summary>
        /// <returns>registered application's clientid</returns>
        public string RegisterApp()
        {
            authContext = new AuthenticationContext(props.Authority);

            // Application Name
            string appName = "CRM for LINQPad";
                        
            ActiveDirectoryClient activeDirectoryClient;

            while (true)
            {
                // Instantiate ActiveDirectoryClient
                activeDirectoryClient = GetActiveDirectoryClientAsApplication(useCurrentUser);

                if (CheckAzureAdPrivilege(activeDirectoryClient))
                    break;
                else
                {
                    // Show error message to indicate user needs to be Company Admin.
                    MessageBox.Show("You need to login as Company Admin or enter ClientId manually.");
                    // Clear the ADAL cache.
                    authContext.TokenCache.Clear();
                    useCurrentUser = false;
                }
            }

            // Check if same name application already exists.
            var existingApp = activeDirectoryClient.Applications
                .Where(x => x.DisplayName == appName)
                .ExecuteAsync().Result.CurrentPage.FirstOrDefault();

            // If it is already registered, then return existing clientid.
            if (existingApp != null)
                return existingApp.AppId;

            // Instantiate Application to Azure AD.
            IApplication myapp = new Microsoft.Azure.ActiveDirectory.GraphClient.Application();
            myapp.DisplayName = appName;
            myapp.ReplyUrls.Add("http://localhost/linqpad");
            myapp.PublicClient = true;
            myapp.AvailableToOtherTenants = false;

            // Create the Application to Azure AD.
            activeDirectoryClient.Applications.AddApplicationAsync(myapp).Wait();

            // Obtain the created Application.
            var createdApp = activeDirectoryClient.Applications
                .Where(x => x.DisplayName == appName)
                .ExecuteAsync().Result.CurrentPage.FirstOrDefault();

            // Instantiate Service regarding to the application.
            IServicePrincipal myservice = new ServicePrincipal();
            myservice.AppId = createdApp.AppId;
            myservice.Tags.Add("WindowsAzureActiveDirectoryIntegratedApp");

            // Create the Service.
            activeDirectoryClient.ServicePrincipals.AddServicePrincipalAsync(myservice).Wait();

            // Obtain the created Service.
            var createdService = activeDirectoryClient.ServicePrincipals
                .Where(x => x.DisplayName == appName)
                .ExecuteAsync().Result.CurrentPage.FirstOrDefault();

            // Set permissions.
            // Get Microsoft.Azure.ActiveDirectory Service.
            var service1 = activeDirectoryClient.ServicePrincipals
                .Where(x => x.DisplayName == "Microsoft.Azure.ActiveDirectory")
                .ExecuteAsync().Result.CurrentPage.FirstOrDefault();

            // Instantiate UserProfile.Read OAuth2PermissionGrant for the Service
            OAuth2PermissionGrant grant0 = new OAuth2PermissionGrant();
            grant0.ClientId = createdService.ObjectId;
            grant0.ResourceId = service1.ObjectId;
            grant0.ConsentType = "AllPrincipals";
            grant0.Scope = "UserProfile.Read";
            grant0.ExpiryTime = DateTime.Now.AddYears(1);

            // Create the OAuth2PermissionGrant
            activeDirectoryClient.Oauth2PermissionGrants.AddOAuth2PermissionGrantAsync(grant0).Wait();

            // Get Microsoft.CRM Service.
            var service2 = activeDirectoryClient.ServicePrincipals
                .Where(x => x.DisplayName == "Microsoft.CRM")
                .ExecuteAsync().Result.CurrentPage.FirstOrDefault();

            // Instantiate user_impersonation OAuth2PermissionGrant for the Service
            OAuth2PermissionGrant grant = new OAuth2PermissionGrant();
            grant.ClientId = createdService.ObjectId;
            grant.ResourceId = service2.ObjectId;
            grant.ConsentType = "AllPrincipals";
            grant.Scope = "user_impersonation";
            grant.ExpiryTime = DateTime.Now.AddYears(1);

            // Create the OAuth2PermissionGrant
            activeDirectoryClient.Oauth2PermissionGrants.AddOAuth2PermissionGrantAsync(grant).Wait();

            // Create RequiredResourceAccess
            // Instantiate ResourceAccess for Microsoft.Azure.ActiveDirectory/UserProfile.Read permission.
            ResourceAccess resourceAccess1 = new ResourceAccess();
            resourceAccess1.Id = service1.Oauth2Permissions.Where(x => x.Value == "UserProfile.Read").First().Id;
            resourceAccess1.Type = "Scope";
            // Instantiate RequiredResourceAccess and assign the ResourceAccess
            RequiredResourceAccess requiredresourceAccess1 = new RequiredResourceAccess();
            requiredresourceAccess1.ResourceAppId = service1.AppId;
            requiredresourceAccess1.ResourceAccess.Add(resourceAccess1);

            // Instantiate ResourceAccess for Microsoft.CRM/user_impersonation.Read permission.
            ResourceAccess resourceAccess2 = new ResourceAccess();
            resourceAccess2.Id = service2.Oauth2Permissions.Where(x => x.Value == "user_impersonation").First().Id;
            resourceAccess2.Type = "Scope";
            // Instantiate RequiredResourceAccess and assign the ResourceAccess
            RequiredResourceAccess requiredResourceAccess2 = new RequiredResourceAccess();
            requiredResourceAccess2.ResourceAppId = service2.AppId;
            requiredResourceAccess2.ResourceAccess.Add(resourceAccess2);

            // Add RequiredResourceAccess information to the Application
            createdApp.RequiredResourceAccess.Add(requiredresourceAccess1);
            createdApp.RequiredResourceAccess.Add(requiredResourceAccess2);

            // Update the Application
            createdApp.UpdateAsync().Wait();

            // Once all Azure AD work done, clear ADAL cache again in case user logged in as different user.
            authContext.TokenCache.Clear();

            // Return AppId (ClientId)
            return createdApp.AppId;
        }

        /// <summary>
        /// Check if login user has enough privilege for Azure AD to register application.
        /// </summary>
        /// <param name="activeDirectoryClient"></param>
        /// <returns>true if user has enough privielge, otherwise false</returns>
        private bool CheckAzureAdPrivilege(ActiveDirectoryClient activeDirectoryClient)
        {
            // Check current user's role for Azure AD.
            var roles = activeDirectoryClient.Me.MemberOf.ExecuteAsync().Result.CurrentPage;
            object role = null;

            // If user has more then (usually 1 or 0) roles, then check if its Company Administrator.
            if (roles.Count != 0)
            {
                role = roles
                .Where(x => x.GetType() == typeof(DirectoryRole))
                .Where(x => (x as DirectoryRole).DisplayName == "Company Administrator").FirstOrDefault();
            }

            // If user doesn't have enough permission, then error out.
            if (role == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Generate new instance of ActiveDirectoryClient
        /// </summary>
        /// <returns>ActiveDirectoryClient</returns>
        public ActiveDirectoryClient GetActiveDirectoryClientAsApplication(bool useCurrentUser = true)
        {
            // Create Graph Endpoint
            string servicePointUri = props.Authority.Replace("https://login.windows.net/", "https://graph.windows.net/").Replace("oauth2/authorize", "");
            Uri serviceRoot = new Uri(servicePointUri);

            // Create ActiveDirectoryClient by specifying how to get AccessToken
            ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
                async () => await AcquireTokenAsyncForApplication(useCurrentUser));

            return activeDirectoryClient;
        }

        /// <summary>
        /// Acquire AccessToken for Azure AD Graph. 
        /// Client ID is obtained from Connected Service.
        /// </summary>
        /// <param name="useCurrentUser">Indicate if using currentuser</param>
        /// <returns>Access Token</returns>
        public async Task<string> AcquireTokenAsyncForApplication(bool useCurrentUser)
        {
            AuthenticationContext authContext = new AuthenticationContext(props.Authority);

            string token = "";

            // If using current user, then get credential from CrmProperties, otherwise popup for authentication.
            if (useCurrentUser)
                token = authContext.AcquireToken("https://graph.windows.net", "2ad88395-b77d-4561-9441-d0e40824f9bc",
                    new UserCredential(props.UserName, props.Password)).AccessToken;
            else
                token = authContext.AcquireToken("https://graph.windows.net", "2ad88395-b77d-4561-9441-d0e40824f9bc",
                   new Uri("app://5d3e90d6-aa8e-48a8-8f2c-58b45cc67315")).AccessToken;

            return token;
        }

        #endregion

        /// <summary>
        /// Generate Context and Early bound class file by using CrmSvcUtil.exe.
        /// Then compile it into an assembly.
        /// </summary>
        private void LoadData()
        {
            // Generate code by using DataSvcUtil.exe
            string code = GenerateCode(props);
            
            // Compile the code into the assembly. To avoid duplicate name for each connection, hash entire URL to make it unique.
            BuildAssembly(code, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, props.OrgUri.GetHashCode() + "RestContext.dll"));

            // Then delete generated file.
            File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DataContext.cs"));

            // Update message.
            Message = "Loading Complete. Click Exit and wait a while until Linq Pad displays full Schema information.";           
        }

        /// <summary>
        /// Generate context for LinqPad by using CrmSvcUtil.exe. In this example, we generate CrmOrganizationServiceContext
        /// so that context has all common methods from OrganizationService. However if you prefer to have just Linq capabilities, 
        /// you are also able to generate OrganizationServiceContext only. Please refer to https://msdn.microsoft.com/en-us/library/gg695792.aspx
        /// for more detail about CrmSvcUtil too.
        /// </summary>
        /// <param name="props">CRM Properties</param>
        /// <returns>Generate code</returns>
        private string GenerateCode(CrmProperties props)
        {            
            // Create Process
            Process p = new Process();

            p.StartInfo.UseShellExecute = false;
            // Specify CrmSvcUtil.exe as process name.
            p.StartInfo.FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DataSvcUtil.exe");
            // Do not display window
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            // Assign argumemnt for CrmSvcUtil. This format works for all environment.
            p.StartInfo.Arguments =
                String.Format(@"/version:1.0 /language:CSharp /in:""{0}"" /out:""{1}""",
                metadataFilePath,
                Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DataContext.cs"));

            // Execute and wait until it complited.
            p.Start();
            p.WaitForExit();

            // Read generate file and return it.
            return System.IO.File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DataContext.cs"));
        }

        /// <summary>
        /// Generate an assembly
        /// </summary>
        /// <param name="code">code to be compiled</param>
        /// <param name="name">assembly name</param>
        private void BuildAssembly(string code, string name)
        {
            // Use the CSharpCodeProvider to compile the generated code:
            CompilerResults results;
            using (var codeProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } }))
            {
                var options = new CompilerParameters(
                    "System.dll System.Core.dll System.Xml.dll System.Runtime.Serialization.dll System.Data.Services.dll".Split(' '),
                    name,
                    false);
                // Force load Microsoft.Xrm.Sdk assembly.
                options.ReferencedAssemblies.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Microsoft.Data.Services.Client.dll"));

                // Compile
                results = codeProvider.CompileAssemblyFromSource(options, code);
            }
            if (results.Errors.Count > 0)
                throw new Exception
                    ("Cannot compile typed context: " + results.Errors[0].ErrorText + " (line " + results.Errors[0].Line + ")");
        }
     
        #endregion

        #region Command
        
        /// <summary>
        /// Login by using CrmLogin.
        /// </summary>
        public RelayCommand LoginCommand
        {
            get
            {
                return new RelayCommand(async () =>
                {
                    IsLoading = true;
                    // First of all, login to CRM.
                    LoginToCrm();

                    // Wait until all CrmProperties are set.
                    while (IsLoading)
                    {
                        await Task.Delay(10);
                    }

                    IsLoading = true;

                    // Register app to Azure AD if no ClientId set and CRMOL.
                    if (String.IsNullOrEmpty(props.ClientId))
                    {
                        if (props.AuthenticationProviderType == "OnlineFederation")
                        {
                            LoadMessage = "Registering Application....";
                            props.ClientId = await Task.Run(() => RegisterApp());
                        }                       
                    }

                    // Then download metadata
                    LoadMessage = "Downloading Metadata....";
                    await DownloadMetadata();

                    // Then generate context code
                    LoadMessage = "Generating Context....";
                    await Task.Run(() => LoadData());

                    XDocument xdoc = XDocument.Parse(System.IO.File.ReadAllText(metadataFilePath));
                    contextName = xdoc.Descendants("{http://schemas.microsoft.com/ado/2007/05/edm}EntityContainer").First().Attribute("Name").Value;

                    // Set the assembly name and Context class name.
                    props._cxInfo.CustomTypeInfo.CustomAssemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, props.OrgUri.GetHashCode() + "RestContext.dll");
                    props._cxInfo.CustomTypeInfo.CustomTypeName = String.Format("Microsoft.Crm.Sdk.Data.Services.{0}", contextName);

                    IsLoading = false;
                    IsLoaded = true;
                });
            }
        }

        /// <summary>
        /// Update the assembly with latest Metadata
        /// </summary>
        public RelayCommand LoadCommand
        {
            get
            {
                return new RelayCommand(async () =>
                {
                    // Handel return. 
                    IsLoading = true;

                    // Download metadata
                    LoadMessage = "Downloading Metadata....";
                    await DownloadMetadata();

                    // Then generate context code
                    LoadMessage = "Generating Context....";
                    await Task.Run(() => LoadData());

                    // Finally set context
                    XDocument xdoc = XDocument.Parse(System.IO.File.ReadAllText(metadataFilePath));
                    contextName = xdoc.Descendants("{http://schemas.microsoft.com/ado/2007/05/edm}EntityContainer").First().Attribute("Name").Value;

                    // Set the assembly name and Context class name.
                    props._cxInfo.CustomTypeInfo.CustomAssemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, props.OrgUri.GetHashCode() + "RestContext.dll");
                    props._cxInfo.CustomTypeInfo.CustomTypeName = String.Format("Microsoft.Crm.Sdk.Data.Services.{0}", contextName);

                    IsLoading = false;
                    IsLoaded = true;
                });
            }
        }
        #endregion
    }    
}
